/*-------------------------------------------------------------------------
 *
 * pg_get_line.c
 *	  使用可扩展结果缓冲区的 fgets()
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/common/pg_get_line.c
 *
 *-------------------------------------------------------------------------
 */
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <setjmp.h>

#include "common/string.h"
#include "lib/stringinfo.h"


/*
 * pg_get_line()
 *
 * 这个函数旨在等效于 fgets()，不同之处在于它不是
 * 读取到调用者提供的固定大小缓冲区，而是读取到
 * 一个 palloc'd（在前端实际上是 malloc'd）字符串中，
 * 该字符串根据需要调整大小以处理无限长的输入行。
 * 调用者负责在适当的时候释放结果字符串的内存。
 *
 * 与 fgets() 一样，如果发生读取错误或在 EOF 之前
 * 没有可用字符，则返回 NULL。调用者可以通过检查
 * ferror(stream) 来区分这些情况。
 *
 * 由于这个函数旨在等效于 fgets()，因此尾随的新行
 * （如果有的话）不会被去除。调用者可能希望应用 
 * pg_strip_crlf()。
 *
 * 请注意，虽然 I/O 错误会返回给调用者处理，
 * 但 palloc'd 缓冲区的 OOM 条件不会体现出来；
 * 在 stringinfo.c 中会有一个 ereport(ERROR) 或 exit(1)。
 *
 * 另外，请注意，palloc'd 缓冲区通常比严格所需的
 * 要长得多，因此不建议使用此函数收集大量长存活的数据。
 * 更少占用内存的选择是使用 pg_get_line_buf() 或 
 * pg_get_line_append() 在循环中，然后 pstrdup() 每一行。
 *
 * 可以选择提供 prompt_ctx，以允许通过现有的 
 * SIGINT 信号处理程序取消此函数，该处理程序只有在 
 * *(prompt_ctx->enabled) 为真时才会长跳转到指定位置。
 * 如果被取消，函数将返回 NULL，并且 prompt_ctx->canceled
 * 被设置为真。
 */
char * pg_get_line(FILE *fc_stream, PromptInterruptContext *fc_prompt_ctx)
{
	StringInfoData fc_buf;

	initStringInfo(&fc_buf);

	if (!pg_get_line_append(fc_stream, &fc_buf, fc_prompt_ctx))
	{
		/* 确保 free() 不会干扰 errno */
		int			fc_save_errno = errno;

		pfree(fc_buf.data);
		errno = fc_save_errno;
		return NULL;
	}

	return fc_buf.data;
}

/*
 * pg_get_line_buf()
 *
 * 这个函数的行为类似于 pg_get_line()，因此也类似于
 * fgets()，不同之处在于收集的数据是在调用者提供的
 * StringInfo 缓冲区中返回。这是一个方便的 API，
 * 适合那些只想逐行读取和处理数据的代码，不受行长度
 * 的人为限制。
 *
 * 如果成功收集到了一行（包括在 EOF 处未以新行结束的行），
 * 则返回 true。如果发生 I/O 错误或 EOF 之前没有数据可用，
 * 则返回 false。（检查 ferror(stream) 以区分这些情况。）
 *
 * 在 false 结果的情况下，buf 被重置为空。
 */
bool pg_get_line_buf(FILE *fc_stream, StringInfo fc_buf)
{
	/* 我们只需要丢弃来自上一个调用的任何数据 */
	resetStringInfo(fc_buf);
	return pg_get_line_append(fc_stream, fc_buf, NULL);
}

/*
 * pg_get_line_append()
 *
 * 这与 pg_get_line() 的行为类似，因此也与 fgets() 类似，
 * 只是收集到的数据会附加到 *buf 中已有的内容上。
 * 当调用者希望将一些行合并在一起时，比如实现反斜杠续行，这一点尤为实用，
 * 相对于 pg_get_line_buf() 来说。
 *
 * 如果成功收集到一行（包括在 EOF 时遇到非换行符终止的行），则返回 true。
 * 如果发生 I/O 错误或在 EOF 前没有可用数据，则返回 false。
 * （检查 ferror(stream) 以区分这些情况。）
 *
 * 在 false 结果的情况下，*buf 的内容在逻辑上未被修改，
 * 尽管可能已经调整了缓存区的大小。
 *
 * prompt_ctx 可以选择性提供，以允许通过现有的 SIGINT 信号处理程序取消该函数，
 * 该处理程序将在 *(prompt_ctx->enabled) 为 true 时跳转到指定位置。
 * 如果被取消，该函数返回 false，并且 prompt_ctx->canceled 被设置为 true。
 */
bool pg_get_line_append(FILE *fc_stream, StringInfo fc_buf,
				   PromptInterruptContext *fc_prompt_ctx)
{
	int			fc_orig_len = fc_buf->len;

	if (fc_prompt_ctx && sigsetjmp(*((sigjmp_buf *) fc_prompt_ctx->jmpbuf), 1) != 0)
	{
		/* 使用 longjmp 到达这里 */
		fc_prompt_ctx->canceled = true;
		/* 丢弃我们在检测到错误之前收集的任何数据 */
		fc_buf->len = fc_orig_len;
		fc_buf->data[fc_orig_len] = '\0';
		return false;
	}

	/* 循环直到换行符或 EOF/错误 */
	for (;;)
	{
		char	   *fc_res;

		/* 在等待输入时启用 longjmp */
		if (fc_prompt_ctx)
			*(fc_prompt_ctx->enabled) = true;

		/* 读取一些数据，将其附加到我们已有的内容上 */
		fc_res = fgets(fc_buf->data + fc_buf->len, fc_buf->maxlen - fc_buf->len, fc_stream);

		/* 再次禁用 longjmp，如果 fgets 失败则中断 */
		if (fc_prompt_ctx)
			*(fc_prompt_ctx->enabled) = false;

		if (fc_res == NULL)
			break;

		/* 收到了数据，因此更新 buf->len */
		fc_buf->len += strlen(fc_buf->data + fc_buf->len);

		/* 如果我们已经收集到一个换行符，则完成 */
		if (fc_buf->len > fc_orig_len && fc_buf->data[fc_buf->len - 1] == '\n')
			return true;

		/* 在缓冲区中多留一些空间，然后循环读取更多数据 */
		enlargeStringInfo(fc_buf, 128);
	}

	/* 检查 I/O 错误和 EOF */
	if (ferror(fc_stream) || fc_buf->len == fc_orig_len)
	{
		/* 丢弃我们在检测到错误之前收集的任何数据 */
		fc_buf->len = fc_orig_len;
		fc_buf->data[fc_orig_len] = '\0';
		return false;
	}

	/* EOF 时没有换行符，但我们确实收集了一些数据 */
	return true;
}
